<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"chentianming11.github.io","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":"default"},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":{"enable":true,"onlypost":false,"loadingImg":"/uploads/loading.gif"},"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="从《mysql存储引擎InnoDB详解，从底层看清InnoDB数据结构》中，我们已经知道了数据页内各个记录是按主键正序排列并组成了一个单向链表的，并且各个数据页之间形成了双向链表。在数据页内，通过页目录，根据主键可以快速定位到一条记录。这一章中，我们深入理解一下mysql索引实现。">
<meta property="og:type" content="article">
<meta property="og:title" content="你真的理解索引吗？从数据结构层面解析mysql索引原理">
<meta property="og:url" content="https://chentianming11.github.io/posts/2462378631/index.html">
<meta property="og:site_name" content="伍六七">
<meta property="og:description" content="从《mysql存储引擎InnoDB详解，从底层看清InnoDB数据结构》中，我们已经知道了数据页内各个记录是按主键正序排列并组成了一个单向链表的，并且各个数据页之间形成了双向链表。在数据页内，通过页目录，根据主键可以快速定位到一条记录。这一章中，我们深入理解一下mysql索引实现。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E6%95%B0%E6%8D%AE%E9%A1%B5%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E8%A1%8C%E6%A0%BC%E5%BC%8F%E7%AE%80%E5%8C%96.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E8%AE%B0%E5%BD%95%E6%94%BE%E5%88%B0%E9%A1%B5%E9%87%8C%E8%BE%B9%E7%9A%84%E7%A4%BA%E6%84%8F%E5%9B%BE.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E5%AD%98%E6%BB%A1%E8%AE%B0%E5%BD%95%E7%9A%84%E6%95%B0%E6%8D%AE%E9%A1%B5.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E9%A1%B5%E5%88%86%E8%A3%82.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B01.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B02.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B03.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B04.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B05.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E4%BA%8C%E7%BA%A7%E7%B4%A2%E5%BC%95.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E5%86%85%E8%8A%82%E7%82%B9%E4%B8%AD%E7%9B%AE%E5%BD%95%E9%A1%B9%E8%AE%B0%E5%BD%95%E7%9A%84%E5%94%AF%E4%B8%80%E6%80%A7.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/MyISAM%E6%95%B0%E6%8D%AE%E6%96%87%E4%BB%B6.webp">
<meta property="og:image" content="https://chentianming11.github.io/images/mysql/%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95%E7%9A%84%E7%A4%BA%E6%84%8F%E5%9B%BE.webp">
<meta property="article:published_time" content="2020-06-14T09:54:50.000Z">
<meta property="article:modified_time" content="2020-08-06T14:05:52.161Z">
<meta property="article:author" content="陈添明">
<meta property="article:tag" content="mysql">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://chentianming11.github.io/images/mysql/%E6%95%B0%E6%8D%AE%E9%A1%B5%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84.webp">

<link rel="canonical" href="https://chentianming11.github.io/posts/2462378631/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>你真的理解索引吗？从数据结构层面解析mysql索引原理 | 伍六七</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">伍六七</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">一起学Java</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签<span class="badge">6</span></a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类<span class="badge">3</span></a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档<span class="badge">17</span></a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

  <a href="https://github.com/chentianming11" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://chentianming11.github.io/posts/2462378631/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.jpg">
      <meta itemprop="name" content="陈添明">
      <meta itemprop="description" content="一个爱看国漫的程序猿">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="伍六七">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          你真的理解索引吗？从数据结构层面解析mysql索引原理
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2020-06-14 17:54:50" itemprop="dateCreated datePublished" datetime="2020-06-14T17:54:50+08:00">2020-06-14</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2020-08-06 22:05:52" itemprop="dateModified" datetime="2020-08-06T22:05:52+08:00">2020-08-06</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/" itemprop="url" rel="index"><span itemprop="name">数据库</span></a>
                </span>
            </span>

          
  
  <span class="post-meta-item">
    
      <span class="post-meta-item-icon">
        <i class="far fa-comment"></i>
      </span>
      <span class="post-meta-item-text">Valine：</span>
    
    <a title="valine" href="/posts/2462378631/#valine-comments" itemprop="discussionUrl">
      <span class="post-comments-count valine-comment-count" data-xid="/posts/2462378631/" itemprop="commentCount"></span>
    </a>
  </span>
  
  <br>
            <span class="post-meta-item" title="本文字数">
              <span class="post-meta-item-icon">
                <i class="far fa-file-word"></i>
              </span>
                <span class="post-meta-item-text">本文字数：</span>
              <span>7.7k</span>
            </span>
            <span class="post-meta-item" title="阅读时长">
              <span class="post-meta-item-icon">
                <i class="far fa-clock"></i>
              </span>
                <span class="post-meta-item-text">阅读时长 &asymp;</span>
              <span>19 分钟</span>
            </span>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <p>从《<a href="https://juejin.im/post/6844904190477598733" target="_blank" rel="noopener">mysql存储引擎InnoDB详解，从底层看清InnoDB数据结构</a>》中，我们已经知道了<strong>数据页内各个记录是按主键正序排列并组成了一个单向链表的，并且各个数据页之间形成了双向链表</strong>。在数据页内，通过<code>页目录</code>，根据主键可以快速定位到一条记录。这一章中，我们深入理解一下mysql索引实现。</p>
<a id="more"></a>

<blockquote>
<p>本文主要内容是根据掘金小册《从根儿上理解 MySQL》整理而来。如想详细了解，建议购买掘金小册阅读。</p>
</blockquote>
<h2 id="索引数据结构"><a href="#索引数据结构" class="headerlink" title="索引数据结构"></a>索引数据结构</h2><p>先回顾一下上一章节中<code>数据页基本结构</code>：<br><img data-src="https://chentianming11.github.io/images/mysql/%E6%95%B0%E6%8D%AE%E9%A1%B5%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84.webp" alt="数据页基本结构"><br>从上图可以推断出，查询某条记录关键步骤只有2个：</p>
<ol>
<li>定位到数据页</li>
<li>定位到记录</li>
</ol>
<p>如果没有索引，查询某条记录只能先依次遍历数据页，确定记录所在的数据页之后；再从数据页中通过<code>页目录</code>定位到具体的记录，这样做效率肯定是很低的。</p>
<p>为了方便说明，先建一张示例表：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; CREATE TABLE index_demo(</span><br><span class="line">    -&gt;     c1 INT,</span><br><span class="line">    -&gt;     c2 INT,</span><br><span class="line">    -&gt;     c3 CHAR(1),</span><br><span class="line">    -&gt;     PRIMARY KEY(c1)</span><br><span class="line">    -&gt; ) ROW_FORMAT = Compact;</span><br><span class="line">Query OK, 0 rows affected (0.03 sec)</span><br></pre></td></tr></table></figure>

<p>为了展示便方便，行格式中只展示<code>record_type</code>、<code>next_record</code>和<code>实际各列的值</code>。<br><img data-src="https://chentianming11.github.io/images/mysql/%E8%A1%8C%E6%A0%BC%E5%BC%8F%E7%AE%80%E5%8C%96.webp" alt="行格式简化"><br>把一些记录放到页里边的示意图就是：<br><img data-src="https://chentianming11.github.io/images/mysql/%E8%AE%B0%E5%BD%95%E6%94%BE%E5%88%B0%E9%A1%B5%E9%87%8C%E8%BE%B9%E7%9A%84%E7%A4%BA%E6%84%8F%E5%9B%BE.webp" alt="记录放到页里边的示意图"></p>
<p>上面提到过，<strong>数据页中的记录是按主键正序排列的</strong>。实际上就是为了能够使用<strong>二分查找法</strong>快速定位一条记录。同理，要想快速定位一个数据页，也得保证各个数据页是按顺序排序的。排序的规则就是<strong>后一个数据页的最小主键必须大于当前数据页的最大主键</strong>。这样实际上就保证了，所有记录的主键都是正序排列的了。</p>
<h3 id="页分裂"><a href="#页分裂" class="headerlink" title="页分裂"></a>页分裂</h3><p>假设每个数据页最多只能存放3条记录。现在<code>index_demo</code>插入了3条记录 <code>(1, 4, &#39;u&#39;), (3, 9, &#39;d&#39;), (5, 3, &#39;y&#39;)</code>。<br><img data-src="https://chentianming11.github.io/images/mysql/%E5%AD%98%E6%BB%A1%E8%AE%B0%E5%BD%95%E7%9A%84%E6%95%B0%E6%8D%AE%E9%A1%B5.webp" alt="存满记录的数据页"><br>然后，再向<code>index_demo</code>插入一条记录<code>(4, 4, &#39;a&#39;)</code>。由于每个数据页最多只能存放3条记录，并且还要保证所有记录主键是按主键正序排列的。mysql会新建一个页面(假设是页28)，然后将主键值为5的记录移动到页28中，最后再把主键值为4的记录插入到页10中。<br><img data-src="https://chentianming11.github.io/images/mysql/%E9%A1%B5%E5%88%86%E8%A3%82.webp" alt="页分裂"><br>简单来说，<strong>当向一个已经存满记录的数据页插入新记录时，mysql会以新插入记录的位置为界，把当前页面分裂为2个页面，最后再将新记录插入进去</strong>。</p>
<h3 id="mysql索引实现"><a href="#mysql索引实现" class="headerlink" title="mysql索引实现"></a>mysql索引实现</h3><p>假设<code>index_demo</code>已经存在多条记录，数据页结构如下所示：<br><img data-src="https://chentianming11.github.io/images/mysql/%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B01.webp" alt="索引实现"><br>为了能够使用<code>二分法</code>快速查找数据页，我们可以给每个数据页建一个目录项，每个目录项主要包含两部分数据：</p>
<ol>
<li>页的用户记录中最小的主键值，我们用<code>key</code>来表示。</li>
<li>页号，我们用<code>page_no</code>表示。</li>
</ol>
<p><img data-src="https://chentianming11.github.io/images/mysql/%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B02.webp" alt="索引实现"><br>在mysql中，这些目录项其实就是另一类型的数据记录，称为<code>目录项数据记录</code>（record_type=1）,<code>目录项数据记录</code>也是存储在<code>页</code>中的，同一页中的<code>目录项数据记录</code>也可以通过<code>页目录</code>快速定位。<br><img data-src="https://chentianming11.github.io/images/mysql/%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B03.webp" alt="索引实现"></p>
<p>虽然<code>目录项记录</code>基本<strong>只存储了主键值和页号</strong>。但是当表中的数据很多时，一个<code>数据页</code>肯定是无法保存所有的<code>目录项记录</code>的。因此存储<code>目录项记录</code>的<code>数据页</code>实际上可能有很多个。<br><img data-src="https://chentianming11.github.io/images/mysql/%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B04.webp" alt="索引实现"><br>这个时候，我们就需要快速定位存储<code>目录项记录</code>的<code>数据页</code>了。实际上，我们只需要生成<strong>更高级的目录即可，同时保证最高一级的<code>目录项记录</code>的<code>数据页</code>只有一个</strong>。这样就能根据主键从上到下快速定位到一条记录了。<br><img data-src="https://chentianming11.github.io/images/mysql/%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B05.webp" alt="索引实现"></p>
<p>实际上，上面的结构就是一颗B+树。<strong>实际的用户记录其实都存放在B+树的<code>叶子节点</code>上，而<code>非叶子节点</code>存放的是目录项</strong>。</p>
<h4 id="聚簇索引"><a href="#聚簇索引" class="headerlink" title="聚簇索引"></a>聚簇索引</h4><p>上面介绍的索引实际上就是聚簇索引，它有两个特点：</p>
<ol>
<li>使用主键值的大小进行记录和页的排序，这包括三个方面的含义：<ol>
<li>页内的记录是按照主键的大小顺序排成一个单向链表。</li>
<li>各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表。</li>
<li>存放目录项记录的页分为不同的层次，在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表。</li>
</ol>
</li>
<li>B+树的叶子节点存储的是完整的用户记录。</li>
</ol>
<p>InnoDB存储引擎会自动根据主键创建聚簇索引。同时，聚簇索引就是InnoDB存储引擎中数据的存储方式（所有的用户记录都存储在了叶子节点），也就是所谓的<strong>索引即数据，数据即索引</strong>。</p>
<h4 id="二级索引"><a href="#二级索引" class="headerlink" title="二级索引"></a>二级索引</h4><p>在实际场景中，我们更多的是为某个列建立二级索引。实际上，二级索引和聚簇索引实现的原理一样的。主要的区别只有2个：</p>
<ol>
<li>使用<code>索引列的值</code>的大小进行记录和页的排序。</li>
<li>B+树的叶子节点存储的是对应记录的主键值。</li>
</ol>
<p>如图是以<code>c2</code>列建立的二级索引：<br><img data-src="https://chentianming11.github.io/images/mysql/%E4%BA%8C%E7%BA%A7%E7%B4%A2%E5%BC%95.webp" alt="二级索引"></p>
<p>由于B+树的叶子节点存储的是对应记录的主键值。如果我们要查询完成记录的话，在拿到主键之后，再需要再到<code>聚簇索引</code>中查出用户记录，这个过程也叫<code>回表</code>。</p>
<h4 id="联合索引"><a href="#联合索引" class="headerlink" title="联合索引"></a>联合索引</h4><p>在实际场景中，经常也出现为多个列建立一个索引的情况，这种索引也称为<code>联合索引</code>。<code>联合索引</code>本质上也是二级索引，区别仅仅在于由一个列变为多个列而已。简单来说就是<strong>同时以多个列的大小作为排序规则，也就是同时为多个列建立索引</strong>。比如我们为<code>c2</code>和<code>c3</code>列建立联合索引：</p>
<ol>
<li>先把各个记录和页按照c2列进行排序。</li>
<li>在记录的c2列相同的情况下，采用c3列进行排序。</li>
</ol>
<p><img data-src="https://chentianming11.github.io/images/mysql/%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95.webp" alt="联合索引"></p>
<h2 id="InnoDB的B-树索引的注意事项"><a href="#InnoDB的B-树索引的注意事项" class="headerlink" title="InnoDB的B+树索引的注意事项"></a>InnoDB的B+树索引的注意事项</h2><h3 id="根节点不变性"><a href="#根节点不变性" class="headerlink" title="根节点不变性"></a>根节点不变性</h3><p>上面介绍B+树的时候，为了理解方便，采用自下而上的方式介绍。实际上，B+树的形成过程如下：</p>
<ol>
<li>每次为某个表创建<code>B+</code>索引的时候，都会为这个索引创建一个根节点页面。当表中没有记录时，每个B+根节点既没有用户记录，也没有目录项记录。</li>
<li>随后向表中插入用户记录时，先把用户记录存储到根节点中。</li>
<li>当根节点空间用完后，再次插入数据。会将根节点数据复制到一个新页中，再对这个新页进行<code>页分裂</code>操作。此时，根节点自动升级为存储目录项记录的页。</li>
</ol>
<p>可以看出，<strong>一个B+树索引的根节点自诞生之日起，便不会再移动</strong>。</p>
<h3 id="内节点中目录项记录的唯一性"><a href="#内节点中目录项记录的唯一性" class="headerlink" title="内节点中目录项记录的唯一性"></a>内节点中目录项记录的唯一性</h3><p>我们知道B+树索引的内节点中目录项记录的内容是索引列+页号的搭配，但是这个搭配对于二级索引来说有点儿不严谨。为了保证内节点目录项记录的唯一性，目录项还需要存储主键值数据。也就是说，目录项记录的内容包含<code>索引列的值</code>、<code>主键值</code>和<code>页号</code>。<br><img data-src="https://chentianming11.github.io/images/mysql/%E5%86%85%E8%8A%82%E7%82%B9%E4%B8%AD%E7%9B%AE%E5%BD%95%E9%A1%B9%E8%AE%B0%E5%BD%95%E7%9A%84%E5%94%AF%E4%B8%80%E6%80%A7.webp" alt="内节点中目录项记录的唯一性"></p>
<h2 id="MyISAM中的索引方案简单介绍"><a href="#MyISAM中的索引方案简单介绍" class="headerlink" title="MyISAM中的索引方案简单介绍"></a>MyISAM中的索引方案简单介绍</h2><p>我们知道<strong>InnoDB中索引即数据，也就是聚簇索引的那棵B+树的叶子节点中已经把所有完整的用户记录都包含了</strong>，而MyISAM的索引方案虽然也使用树形结构，但是却将<strong>索引和数据分开存储</strong>：</p>
<ul>
<li><code>MyISAM存储引擎</code>把记录按照记录的插入顺序单独存储在<code>数据文件</code>中。这个文件并不划分为若干个数据页，有多少记录就往这个文件中塞多少记录就成了。我们可以通过行号而快速访问到一条记录。<br><img data-src="https://chentianming11.github.io/images/mysql/MyISAM%E6%95%B0%E6%8D%AE%E6%96%87%E4%BB%B6.webp" alt="MyISAM数据文件"></li>
<li><code>MyISAM存储引擎</code>会把索引信息另外存储到<code>索引文件</code>中。<code>MyISAM</code>会单独为表的主键创建一个索引，只不过在<strong>索引的叶子节点中存储的不是完整的用户记录，而是主键值+行号的组合</strong>。也就是先通过索引找到对应的行号，再通过行号去找对应的记录！<br>这一点和<code>InnoDB</code>是完全不相同的，在InnoDB存储引擎中，我们只需要根据主键值对聚簇索引进行一次查找就能找到对应的记录。而在MyISAM中却需要进行一次回表操作，意味着<strong>MyISAM中建立的索引相当于全部都是二级索引</strong>！</li>
<li>如果有需要的话，我们也可以对其它的列分别建立索引或者建立联合索引，原理和InnoDB中的索引差不多，不过在叶子节点处存储的是相应的列+行号。这些索引也全部都是二级索引。</li>
</ul>
<h2 id="索引的使用"><a href="#索引的使用" class="headerlink" title="索引的使用"></a>索引的使用</h2><p>上面介绍了B+索引的原理，接下来介绍如何更好的使用索引。大家都知道索引不是建的越多越好，因为创建索引在空间上和时间上都会付出代价。</p>
<ol>
<li>空间上的代价<br>每创建一个索引，本质上就是要建立一个B+树，创建索引肯定会占用一部分存储空间。</li>
<li>时间上的代价<br>每次对表中的数据进行增删改操作时，都需要去修改各个B+树索引，而B+树索引的记录又是按照<code>索引列的值</code>排序的。每次增删改操作时，不可避免的会破坏原有记录的顺序，所以存储引擎需要额外的时间来进行记录移位、页面分裂等操作来维护记录的顺序。</li>
</ol>
<p>简单来说，<strong>一张表的索引越多，占用的存储空间也会越多，增删改的性能会更差</strong>。</p>
<h3 id="B-树索引适用的条件"><a href="#B-树索引适用的条件" class="headerlink" title="B+树索引适用的条件"></a>B+树索引适用的条件</h3><p>首先创建一张示例表<code>person_info</code>，用来存储人的一些基本信息。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> person_info(</span><br><span class="line">    <span class="keyword">id</span> <span class="built_in">INT</span> <span class="keyword">NOT</span> <span class="literal">NULL</span> auto_increment,</span><br><span class="line">    <span class="keyword">name</span> <span class="built_in">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>,</span><br><span class="line">    birthday <span class="built_in">DATE</span> <span class="keyword">NOT</span> <span class="literal">NULL</span>,</span><br><span class="line">    phone_number <span class="built_in">CHAR</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>,</span><br><span class="line">    country <span class="built_in">varchar</span>(<span class="number">100</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span>,</span><br><span class="line">    PRIMARY <span class="keyword">KEY</span> (<span class="keyword">id</span>),</span><br><span class="line">    <span class="keyword">KEY</span> idx_name_birthday_phone_number (<span class="keyword">name</span>, birthday, phone_number)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<p>简要说明一下：</p>
<ol>
<li><code>id</code>列为主键，自动递增。InnoDB会自动为id列建立聚簇索引。</li>
<li>为<code>name</code>, <code>birthday</code>, <code>phone_number</code>建立了一个联合索引。所以这个二级索引的叶子节点包含了<code>name</code>, <code>birthday</code>, <code>phone_number</code>和<code>id</code>列的值。</li>
</ol>
<p>下面，简要画一下<code>idx_name_birthday_phone_number</code>联合索引的示意图。<br><img data-src="https://chentianming11.github.io/images/mysql/%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95%E7%9A%84%E7%A4%BA%E6%84%8F%E5%9B%BE.webp" alt="联合索引的示意图"><br>从图中可以看出，这个<code>idx_name_birthday_phone_number</code>索引对应的B+树中页面和记录的排序方式就是这样的：</p>
<ol>
<li>先按照<code>name</code>列的值进行排序。</li>
<li>如果<code>name</code>列的值相同，则按照<code>birthday</code>列的值进行排序。</li>
<li>如果<code>birthday</code>列的值也相同，则按照<code>phone_number</code>的值进行排序。</li>
</ol>
<h4 id="全值匹配"><a href="#全值匹配" class="headerlink" title="全值匹配"></a>全值匹配</h4><p>全值匹配指的是<strong>搜索条件中的列和索引列一致</strong>。比如：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> person_info <span class="keyword">WHERE</span> <span class="keyword">name</span> = <span class="string">'Ashburn'</span> <span class="keyword">AND</span> birthday = <span class="string">'1990-09-27'</span> <span class="keyword">AND</span> phone_number = <span class="string">'15123983239'</span>;</span><br></pre></td></tr></table></figure>

<p>在<code>idx_name_birthday_phone_number</code>联合索引上进行全值匹配的查询过程如下：</p>
<ol>
<li>因为B+树的数据页和记录先是按照<code>name</code>列的值进行排序的，所以先可以很快定位name列的值是<code>Ashburn</code>的记录位置。</li>
<li>在<code>name</code>列相同的记录里又是按照<code>birthday</code>列的值进行排序的，所以在<code>name</code>列的值是<code>Ashburn</code>的记录里又可以快速定位<code>birthday</code>列的值是’1990-09-27’的记录。</li>
<li>如果<code>name</code>和<code>birthday</code>列的值都是相同的，那记录是按照phone_number列的值排序的，<strong>所以联合索引中的三个列都可能被用到</strong>。</li>
</ol>
<h4 id="联合索引最左匹配"><a href="#联合索引最左匹配" class="headerlink" title="联合索引最左匹配"></a>联合索引最左匹配</h4><p>其实在搜索语句中不用包含全部联合索引的列，只包含左边的列也能够使用索引，这就是联合索引的<strong>最左匹配原则</strong>。比如：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> person_info <span class="keyword">WHERE</span> <span class="keyword">name</span> = <span class="string">'Ashburn'</span> <span class="keyword">AND</span> birthday = <span class="string">'1990-09-27'</span>;</span><br></pre></td></tr></table></figure>

<p><strong>如果我们想使用联合索引中尽可能多的列，搜索条件中的各个列必须是联合索引中从最左边连续的列</strong>。</p>
<h4 id="前缀匹配"><a href="#前缀匹配" class="headerlink" title="前缀匹配"></a>前缀匹配</h4><p><strong>对于字符串类型的索引列来说，我们只匹配它的前缀也是可以快速定位记录的</strong>。因为字符串比较本质上按一个一个字符比较得出的，也就是说这些字符串的前n个字符，也就是前缀都是排好序的。比如：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> person_info <span class="keyword">WHERE</span> <span class="keyword">name</span> <span class="keyword">LIKE</span> <span class="string">'As%'</span>;</span><br></pre></td></tr></table></figure>

<p>但是如果只给出后缀或者中间的某个字符串，是无法使用索引的，比如这样：<code>%As</code>或者<code>%As%</code>。如果实际场景中碰到要以字符串后缀查询数据的话，可以考虑<code>逆序存储</code>，将后缀匹配转化为前缀匹配。</p>
<h4 id="范围匹配"><a href="#范围匹配" class="headerlink" title="范围匹配"></a>范围匹配</h4><p>因为索引B+树是按照索引列大小排序的，因此按索引列范围查询可以快速查询出数据记录。比如：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> person_info <span class="keyword">WHERE</span> <span class="keyword">name</span> &gt; <span class="string">'Asa'</span> <span class="keyword">AND</span> <span class="keyword">name</span> &lt; <span class="string">'Barlow'</span>;</span><br></pre></td></tr></table></figure>

<p>由于B+树中的数据页和记录是先按<code>name</code>列排序的，所以我们上边的查询过程其实是这样的：</p>
<ol>
<li>找到name值为<code>Asa</code>的记录。</li>
<li>找到name值为<code>Barlow</code>的记录。</li>
<li>由于叶子节点记录本身是一个链表，直接取出范围之内的记录。</li>
<li>回表查询完整记录。</li>
</ol>
<h4 id="精确匹配某一列并范围匹配另外一列"><a href="#精确匹配某一列并范围匹配另外一列" class="headerlink" title="精确匹配某一列并范围匹配另外一列"></a>精确匹配某一列并范围匹配另外一列</h4><p>对于同一个联合索引来说，虽然对多个列都进行范围查找时只能用到最左边那个索引列，但是如果左边的列是精确查找，则右边的列可以进行范围查找，这种场景下依然会使用索引。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> person_info <span class="keyword">WHERE</span> <span class="keyword">name</span> = <span class="string">'Ashburn'</span> <span class="keyword">AND</span> birthday &gt; <span class="string">'1980-01-01'</span> <span class="keyword">AND</span> birthday &lt; <span class="string">'2000-12-31'</span> <span class="keyword">AND</span> phone_number &gt; <span class="string">'15100000000'</span>;</span><br></pre></td></tr></table></figure>

<p>整个查询过程大致如下：</p>
<ol>
<li><code>name = &#39;Ashburn&#39;</code>，对<code>name</code>列进行精确查找，当然可以使用B+树索引了。</li>
<li><code>birthday &gt; &#39;1980-01-01&#39; AND birthday &lt; &#39;2000-12-31&#39;</code>，由于<code>name</code>列是精确查找，所以通过<code>name = &#39;Ashburn&#39;</code>条件查找后得到的结果的name值都是相同的，它们会再按照<code>birthday</code>的值进行排序。所以此时对<code>birthday</code>列进行范围查找是可以用到B+树索引的。</li>
<li><code>phone_number &gt; &#39;15100000000&#39;</code>，通过<code>birthday</code>的范围查找的记录的<code>birthday</code>的值可能不同，所以这个条件无法再利用B+树索引了，只能遍历上一步查询得到的记录。</li>
</ol>
<h4 id="用于排序"><a href="#用于排序" class="headerlink" title="用于排序"></a>用于排序</h4><p>在实际业务场景中，经常需要对查询出来的结果进行排序。一般情况下，只能将记录全部加载到内存中（结果集太大可能使用磁盘存放中间结果），再使用排序算法排序。这种<strong>在内存中或者磁盘上的排序方式统称为文件排序<code>filesort</code>，性能较差</strong>。但是如果<code>order by</code>子句使用到了索引列，就可能避免<code>filesort</code>。比如下面这个查询语句：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> person_info <span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="keyword">name</span>, birthday, phone_number <span class="keyword">LIMIT</span> <span class="number">10</span>;</span><br></pre></td></tr></table></figure>

<p>这个查询结果依次按<code>name</code>、<code>birthday</code>和<code>phone_number</code>排序，而<code>idx_name_birthday_phone_number</code>B+索引树也刚好是按上述规则排好序的，因此只需要直接从索引中提取数据，然后回表即可。<br>需要注意的是，对于联合索引来说，<code>ORDER BY</code>的子句后边的列的顺序也必须跟索引列的顺序一致，否则排序的时候就无法使用索引了。</p>
<h4 id="用于分组"><a href="#用于分组" class="headerlink" title="用于分组"></a>用于分组</h4><p>有时候我们为了方便统计表中的一些信息，会把表中的记录按照某些列进行分组。比如下边这个分组查询：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">name</span>, birthday, phone_number, <span class="keyword">COUNT</span>(*) <span class="keyword">FROM</span> person_info <span class="keyword">GROUP</span> <span class="keyword">BY</span> <span class="keyword">name</span>, birthday, phone_number</span><br></pre></td></tr></table></figure>

<p>和使用B+树索引进行排序是一个道理，分组列的顺序也需要和索引列的顺序一致，也可以只使用索引列中左边的列进行分组。</p>
<h4 id="覆盖索引"><a href="#覆盖索引" class="headerlink" title="覆盖索引"></a>覆盖索引</h4><p>上面提到到，所谓回表就是在二级索引中获取到主键id集合之后，再分别到聚簇索引查询出完整记录，简单来说就是<strong>一次二级索引查询，多次聚簇索引回表</strong>。这意味着<strong>二级索引命中的主键记录越多，需要回表的记录也会也多，整体的性能就会越低</strong>。因此某些查询，宁可使用全表扫描也不使用二级索引。<br>为了更好的使用<code>二级索引+回表</code>的方式进行查询，一般推荐使用<code>limit</code>限制要查询的记录，这样<code>回表</code>的次数也能得到控制。</p>
<p>为了彻底告别回表操作带来的性能损耗，建议：<strong>在查询列表里只包含索引列</strong>，比如这样：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">name</span>, birthday, phone_number <span class="keyword">FROM</span> person_info <span class="keyword">WHERE</span> <span class="keyword">name</span> &gt; <span class="string">'Asa'</span> <span class="keyword">AND</span> <span class="keyword">name</span> &lt; <span class="string">'Barlow'</span></span><br></pre></td></tr></table></figure>

<p>因为只查询<code>name</code>, <code>birthday</code>, <code>phone_number</code>这三个索引列的值，所以就没必要进行回表操作了。我们把这种<strong>只需要用到索引的查询方式称为覆盖索引</strong>。</p>
<h3 id="如何挑选索引"><a href="#如何挑选索引" class="headerlink" title="如何挑选索引"></a>如何挑选索引</h3><p>上面主要介绍了索引的适用场景，接下来我们介绍下建立索引时或者编写查询语句时就应该注意的一些事项。</p>
<h4 id="只为用于搜索、排序或分组的列创建索引"><a href="#只为用于搜索、排序或分组的列创建索引" class="headerlink" title="只为用于搜索、排序或分组的列创建索引"></a>只为用于搜索、排序或分组的列创建索引</h4><p>只为出现在<code>WHERE</code>子句中的列、连接子句中的连接列，或者出现在ORDER BY或GROUP BY子句中的列创建索引。而出现在查询列表中的列就没必要建立索引了。</p>
<h4 id="考虑列的基数"><a href="#考虑列的基数" class="headerlink" title="考虑列的基数"></a>考虑列的基数</h4><p><code>列的基数</code>指的是某一列中不重复数据的个数。，在记录行数一定的情况下，<strong>列的基数越大，该列中的值越分散，列的基数越小，该列中的值越集中</strong>。因此推荐的方式是<strong>为那些列的基数大的列建立索引</strong>，为基数太小列的建立索引效果可能不好。</p>
<h4 id="索引列的类型尽量小"><a href="#索引列的类型尽量小" class="headerlink" title="索引列的类型尽量小"></a>索引列的类型尽量小</h4><p><strong>在表示的整数范围允许的情况下，尽量让索引列使用较小的类型</strong>。原因如下：</p>
<ol>
<li>数据类型越小，在查询时进行的比较操作越快</li>
<li>数据类型越小，索引占用的存储空间就越少，在一个数据页内就可以放下更多的记录，从而减少磁盘I/O带来的性能损耗，也就意味着可以把更多的数据页缓存在内存中，从而加快读写效率。</li>
</ol>
<h4 id="使用前缀索引"><a href="#使用前缀索引" class="headerlink" title="使用前缀索引"></a>使用前缀索引</h4><p>当字段值比较长的时候，建立索引会消耗很多的空间，搜索起来也会很慢。我们可以通过<strong>截取字段的前面一部分内容建立索引</strong>，这个就叫前缀索引。<br>例如：创建一张商户表，因为地址字段比较长，在地址字段上建立前缀索引:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> shop(address <span class="built_in">varchar</span>(<span class="number">120</span>) <span class="keyword">not</span> <span class="literal">null</span>);</span><br></pre></td></tr></table></figure>

<p>问题是，截取多少呢?截取得多了，达不到节省索引存储空间的目的，截取得少了， 重复内容太多，字段的基数会降低。实际场景中，可以通过不同长度的基数与总记录数据基数的比值，选择一个较为合理的截取长度。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="keyword">count</span>(<span class="keyword">distinct</span> <span class="keyword">left</span>(address,<span class="number">10</span>))/<span class="keyword">count</span>(*) <span class="keyword">as</span> sub10,</span><br><span class="line"><span class="keyword">count</span>(<span class="keyword">distinct</span> <span class="keyword">left</span>(address,<span class="number">11</span>))/<span class="keyword">count</span>(*) <span class="keyword">as</span> sub11,</span><br><span class="line"><span class="keyword">count</span>(<span class="keyword">distinct</span> <span class="keyword">left</span>(address,<span class="number">12</span>))/<span class="keyword">count</span>(*) <span class="keyword">as</span> sub12,</span><br><span class="line"><span class="keyword">count</span>(<span class="keyword">distinct</span> <span class="keyword">left</span>(address,<span class="number">13</span>))/<span class="keyword">count</span>(*) <span class="keyword">as</span> sub13</span><br><span class="line"><span class="keyword">from</span> shop;</span><br></pre></td></tr></table></figure>

<h4 id="避免索引列字段参与计算"><a href="#避免索引列字段参与计算" class="headerlink" title="避免索引列字段参与计算"></a>避免索引列字段参与计算</h4><p><strong>如果索引列在比较表达式中不是以单独列的形式出现，而是以某个表达式，或者函数调用形式出现的话，是用不到索引的</strong>。<br>比如有一个整数列<code>my_col</code>，<code>WHERE my_col * 2 &lt; 4</code>查询是不会使用索引的，而<code>WHERE my_col &lt; 4/2</code>能正常使用索引。</p>
<h4 id="主键插入顺序"><a href="#主键插入顺序" class="headerlink" title="主键插入顺序"></a>主键插入顺序</h4><p>我们知道，对于InnoDB来说，数据实际上是按主键大小正序存储在聚簇索引的叶子节点上的。所以如果我们插入的记录的主键值是依次增大的话，那我们每插满一个数据页就换到下一个数据页继续插入。而如果我们插入的主键值忽大忽小的话，就会造成频繁的<code>页分裂</code>，严重影响性能。因此，为了保证性能，需要保证主键是递增的。</p>
<h3 id="无法使用索引的几种情况"><a href="#无法使用索引的几种情况" class="headerlink" title="无法使用索引的几种情况"></a>无法使用索引的几种情况</h3><ol>
<li><code>ORDER BY</code>的子句后边的列的顺序也必须跟索引列的顺序不一致。</li>
<li><code>ASC</code>、<code>DESC</code>混用</li>
<li>排序列包含非同一个索引的列</li>
<li>排序列使用了复杂的表达式</li>
<li>索引列上使用函数<code>(replace\SUBSTR\CONCAT\sum count avg)、表达式、 计算(+ - * /)</code></li>
<li>like 条件中前面带%</li>
<li>字符串不加引号，出现隐式转换</li>
</ol>
<blockquote>
<p>原创不易，觉得文章写得不错的小伙伴，点个赞👍 鼓励一下吧~</p>
</blockquote>

    </div>

    
    
    

      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/mysql/" rel="tag"># mysql</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/posts/786374041/" rel="prev" title="mysql存储引擎InnoDB详解，从底层看清InnoDB数据结构">
      <i class="fa fa-chevron-left"></i> mysql存储引擎InnoDB详解，从底层看清InnoDB数据结构
    </a></div>
      <div class="post-nav-item">
    <a href="/posts/2001481941/" rel="next" title="mysql执行计划Explain详解，再也不用怕sql优化了！">
      mysql执行计划Explain详解，再也不用怕sql优化了！ <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          
    <div class="comments" id="valine-comments"></div>

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#索引数据结构"><span class="nav-number">1.</span> <span class="nav-text">索引数据结构</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#页分裂"><span class="nav-number">1.1.</span> <span class="nav-text">页分裂</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#mysql索引实现"><span class="nav-number">1.2.</span> <span class="nav-text">mysql索引实现</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#聚簇索引"><span class="nav-number">1.2.1.</span> <span class="nav-text">聚簇索引</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#二级索引"><span class="nav-number">1.2.2.</span> <span class="nav-text">二级索引</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#联合索引"><span class="nav-number">1.2.3.</span> <span class="nav-text">联合索引</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#InnoDB的B-树索引的注意事项"><span class="nav-number">2.</span> <span class="nav-text">InnoDB的B+树索引的注意事项</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#根节点不变性"><span class="nav-number">2.1.</span> <span class="nav-text">根节点不变性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#内节点中目录项记录的唯一性"><span class="nav-number">2.2.</span> <span class="nav-text">内节点中目录项记录的唯一性</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#MyISAM中的索引方案简单介绍"><span class="nav-number">3.</span> <span class="nav-text">MyISAM中的索引方案简单介绍</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#索引的使用"><span class="nav-number">4.</span> <span class="nav-text">索引的使用</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#B-树索引适用的条件"><span class="nav-number">4.1.</span> <span class="nav-text">B+树索引适用的条件</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#全值匹配"><span class="nav-number">4.1.1.</span> <span class="nav-text">全值匹配</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#联合索引最左匹配"><span class="nav-number">4.1.2.</span> <span class="nav-text">联合索引最左匹配</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#前缀匹配"><span class="nav-number">4.1.3.</span> <span class="nav-text">前缀匹配</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#范围匹配"><span class="nav-number">4.1.4.</span> <span class="nav-text">范围匹配</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#精确匹配某一列并范围匹配另外一列"><span class="nav-number">4.1.5.</span> <span class="nav-text">精确匹配某一列并范围匹配另外一列</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#用于排序"><span class="nav-number">4.1.6.</span> <span class="nav-text">用于排序</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#用于分组"><span class="nav-number">4.1.7.</span> <span class="nav-text">用于分组</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#覆盖索引"><span class="nav-number">4.1.8.</span> <span class="nav-text">覆盖索引</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#如何挑选索引"><span class="nav-number">4.2.</span> <span class="nav-text">如何挑选索引</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#只为用于搜索、排序或分组的列创建索引"><span class="nav-number">4.2.1.</span> <span class="nav-text">只为用于搜索、排序或分组的列创建索引</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#考虑列的基数"><span class="nav-number">4.2.2.</span> <span class="nav-text">考虑列的基数</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#索引列的类型尽量小"><span class="nav-number">4.2.3.</span> <span class="nav-text">索引列的类型尽量小</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#使用前缀索引"><span class="nav-number">4.2.4.</span> <span class="nav-text">使用前缀索引</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#避免索引列字段参与计算"><span class="nav-number">4.2.5.</span> <span class="nav-text">避免索引列字段参与计算</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#主键插入顺序"><span class="nav-number">4.2.6.</span> <span class="nav-text">主键插入顺序</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#无法使用索引的几种情况"><span class="nav-number">4.3.</span> <span class="nav-text">无法使用索引的几种情况</span></a></li></ol></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="陈添明"
      src="/images/avatar.jpg">
  <p class="site-author-name" itemprop="name">陈添明</p>
  <div class="site-description" itemprop="description">一个爱看国漫的程序猿</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">17</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">3</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">6</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2020</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">陈添明</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-chart-area"></i>
    </span>
    <span title="站点总字数">156k</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-coffee"></i>
    </span>
    <span title="站点阅读时长">6:30</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Gemini</a> 强力驱动
  </div>

        








      </div>
    </footer>
  </div>

  
  <script size="300" alpha="0.6" zIndex="-1" src="//cdn.jsdelivr.net/gh/theme-next/theme-next-canvas-ribbon@1/canvas-ribbon.js"></script>
  <script src="/lib/anime.min.js"></script>
  <script src="//cdn.jsdelivr.net/gh/theme-next/theme-next-pjax@0/pjax.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/lozad@1/dist/lozad.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>

  <script>
var pjax = new Pjax({
  selectors: [
    'head title',
    '#page-configurations',
    '.content-wrap',
    '.post-toc-wrap',
    '.languages',
    '#pjax'
  ],
  switches: {
    '.post-toc-wrap': Pjax.switches.innerHTML
  },
  analytics: false,
  cacheBust: false,
  scrollTo : !CONFIG.bookmark.enable
});

window.addEventListener('pjax:success', () => {
  document.querySelectorAll('script[data-pjax], script#page-configurations, #pjax script').forEach(element => {
    var code = element.text || element.textContent || element.innerHTML || '';
    var parent = element.parentNode;
    parent.removeChild(element);
    var script = document.createElement('script');
    if (element.id) {
      script.id = element.id;
    }
    if (element.className) {
      script.className = element.className;
    }
    if (element.type) {
      script.type = element.type;
    }
    if (element.src) {
      script.src = element.src;
      // Force synchronous loading of peripheral JS.
      script.async = false;
    }
    if (element.dataset.pjax !== undefined) {
      script.dataset.pjax = '';
    }
    if (code !== '') {
      script.appendChild(document.createTextNode(code));
    }
    parent.appendChild(script);
  });
  NexT.boot.refresh();
  // Define Motion Sequence & Bootstrap Motion.
  if (CONFIG.motion.enable) {
    NexT.motion.integrator
      .init()
      .add(NexT.motion.middleWares.subMenu)
      .add(NexT.motion.middleWares.postList)
      .bootstrap();
  }
  NexT.utils.updateSidebarPosition();
});
</script>




  




  
<script src="/js/local-search.js"></script>













    <div id="pjax">
  

  
  <script src="//cdn.jsdelivr.net/npm/quicklink@1/dist/quicklink.umd.js"></script>
  <script>
      window.addEventListener('load', () => {
      quicklink({
        timeout : 3000,
        priority: true,
        ignores : [uri => uri.includes('#'),uri => uri === 'https://chentianming11.github.io/posts/2462378631/',]
      });
      });
  </script>


<script>
NexT.utils.loadComments(document.querySelector('#valine-comments'), () => {
  NexT.utils.getScript('//unpkg.com/valine/dist/Valine.min.js', () => {
    var GUEST = ['nick', 'mail', 'link'];
    var guest = 'nick,mail';
    guest = guest.split(',').filter(item => {
      return GUEST.includes(item);
    });
    new Valine({
      el         : '#valine-comments',
      verify     : false,
      notify     : true,
      appId      : 'SoPxUkme2aynTULaNuIc5TIq-gzGzoHsz',
      appKey     : '4TDP7SMCbgDKTOGGUtpIeA0H',
      placeholder: "留下点足迹吧~",
      avatar     : 'mm',
      meta       : guest,
      pageSize   : '10' || 10,
      visitor    : false,
      lang       : '' || 'zh-cn',
      path       : location.pathname,
      recordIP   : false,
      serverURLs : ''
    });
  }, window.Valine);
});
</script>

    </div>
</body>
</html>
